package edu.csu.smartpark.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.*;

@Component
@Slf4j
/*
* @Description: 发送http，tcp等请求的工具类
* @Author: LZY
* @Date: 2021/6/29 9:33
*/
public class RequestUtil {

    /*
    * @Description: 发送http get请求
    * @Author: LZY
    * @Date: 2021/6/29 9:32
    */
    public static String doGet(String url){
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        StringBuilder result = new StringBuilder();
        try {
            // 创建一个默认配置的httpClient实例
            httpClient = HttpClients.createDefault();
            // 创建httpGet远程连接实例
            HttpGet httpGet = new HttpGet(url);
            // 设置配置请求参数
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000)// 连接主机服务超时时间
                    .setConnectionRequestTimeout(30000)// 请求超时时间
                    .setSocketTimeout(60000)// 数据读取超时时间
                    .build();
            // 为httpGet实例设置配置
            httpGet.setConfig(requestConfig);
            // 执行get请求得到返回对象
            httpResponse = httpClient.execute(httpGet);
            // 通过返回对象获取返回数据
            HttpEntity entity = httpResponse.getEntity();
            // 通过EntityUtils中的toString方法将结果转换为字符串
            result.append(EntityUtils.toString(entity));
        } catch (ClientProtocolException e) {
            log.warn("[RequestUtil] doGet fail, client protocol exception");
            return "";
        } catch (IOException e) {
            log.warn("[RequestUtil] doGet fail, IO exception");
            return "";
        } finally {
            // 关闭资源
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    log.warn("[RequestUtil] close http response fail, IO exception");
                }
            }
            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    log.warn("[RequestUtil] close http client fail, IO exception");
                }
            }
        }
        return result.toString();
    }

    /*
    * @Description: 发送http post请求
    * @Author: LZY
    * @Date: 2021/6/29 9:47
    */
    public static String doPost(String url, Map<String, Object> paramMap){
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        StringBuilder result = new StringBuilder();

        // 创建一个默认配置的httpClient实例
        httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        // 设置配置请求参数
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000)// 连接主机服务超时时间
                .setConnectionRequestTimeout(30000)// 请求超时时间
                .setSocketTimeout(60000)// 数据读取超时时间
                .build();
        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
        // 封装post请求参数
        if (paramMap != null && paramMap.size() > 0){
            List<NameValuePair> nvps = new LinkedList<>();
            Set<Map.Entry<String, Object>> entrySet = paramMap.entrySet();
            Iterator<Map.Entry<String, Object>> iterator = entrySet.iterator();
            while (iterator.hasNext()){
                Map.Entry<String, Object> entry = iterator.next();
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
            }
            // 为httpPost设置封装好的请求参数
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.warn("[RequestUtil] doPost fail, encode post params throw UnsupportedEncodingException");
                return "";
            }
        }

        try {
            // httpClient对象执行post请求,并返回响应参数对象
            httpResponse = httpClient.execute(httpPost);
            // 从响应对象中获取响应内容
            HttpEntity entity = httpResponse.getEntity();
            result.append(EntityUtils.toString(entity));
        }catch (ClientProtocolException e) {
            log.warn("[RequestUtil] doGet fail, client protocol exception");
            return "";
        } catch (IOException e) {
            log.warn("[RequestUtil] doGet fail, IO exception");
            return "";
        } finally {
            // 关闭资源
            if (httpResponse != null) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    log.warn("[RequestUtil] close http response fail, IO exception");
                }
            }
            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    log.warn("[RequestUtil] close http client fail, IO exception");
                }
            }
        }
        return result.toString();
    }

    public static String doTcp(String ip, String cmd){
        try {
            Socket socket = new Socket(ip, 8131);
            // 构造请求头发送tcp请求
            int len =  cmd.getBytes().length;
            byte[] header = {'V','Z',0,0,0,0,0,0};
            header[4] += (byte) ((len >>24) & 0xFF);
            header[5] += (byte) ((len >>16) & 0xFF);
            header[6] += (byte) ((len >>8) & 0xFF);
            header[7] += (byte) (len & 0xFF);
            OutputStream out = socket.getOutputStream();
            out.write(header);
            out.write(cmd.getBytes());
            int snLen = recvPacketSize(socket);
            if (snLen <= 0){
                log.warn("get tcp message fail");
                return "";
            }
            byte[] data  = new byte[snLen];
            int recvLen = recvBlock(socket, data, snLen);
            String sn = new String(data, 0, recvLen);
            return sn;
        }catch (Exception e){
            log.warn("send tcp request to {} Error: {}", ip, e.getMessage());
            return "";
        }
    }

    private static int recvPacketSize(Socket socket){
        byte[] header = new byte[8];
        int recvLen = recvBlock(socket, header, 8);
        if(recvLen <=0)
        {
            return -1;
        }

        if(header[0] != 'V' ||header[1] != 'Z')
        {
            return -1;
        }

        if(header[2] == 1)
        {
            return 0;
        }
        return convBytesToInt(header,4);
    }

    private static int recvBlock(Socket socket, byte[] buff, int len)
    {
        try
        {
            InputStream in = socket.getInputStream();
            int totleRecvLen = 0;
            int recvLen;
            while(totleRecvLen < len)
            {
                recvLen = in.read(buff, totleRecvLen, len - totleRecvLen);
                totleRecvLen += recvLen;
            }
            return len;
        }
        catch(Exception e)
        {
            log.warn("recvBlock timeout!");
            return -1;
        }
    }

    private static int convBytesToInt(byte[] buff,int offset) {
        int len,byteValue;
        len = 0;
        byteValue = (0x000000FF & ((int)buff[offset]));
        len += byteValue<<24;
        byteValue = (0x000000FF & ((int)buff[offset+1]));
        len += byteValue<<16;
        byteValue = (0x000000FF & ((int)buff[offset+2]));
        len += byteValue<<8;
        byteValue = (0x000000FF & ((int)buff[offset+3]));
        len += byteValue;
        return len;
    }
}
